import justifiedLayout from 'justified-layout';

/**
 * Generate the layout matrix.
 *
 * If we are in square mode, do this manually to get non-uniformity.
 * Otherwise, use flickr/justified-layout (at least for now).
 */
export function getLayout(
  input: {
    width: number;
    height: number;
    forceSquare: boolean;
  }[],
  opts: {
    rowWidth: number;
    rowHeight: number;
    squareMode: boolean;
    numCols: number;
    allowBreakout: boolean;
    seed: number;
  },
): {
  top: number;
  left: number;
  width: number;
  height: number;
  rowHeight?: number;
}[] {
  if (input.length === 0) return [];

  if (!opts.squareMode) {
    return justifiedLayout(input, {
      containerPadding: 0,
      boxSpacing: 0,
      containerWidth: opts.rowWidth,
      targetRowHeight: opts.rowHeight,
      targetRowHeightTolerance: 0.1,
    }).boxes;
  }

  // RNG
  const rand = mulberry32(opts.seed);

  // Binary flags
  const FLAG_USE = 1 << 0;
  const FLAG_USED = 1 << 1;
  const FLAG_USE4 = 1 << 2;
  const FLAG_USE6 = 1 << 3;
  const FLAG_BREAKOUT = 1 << 4;

  // Create 2d matrix to work in
  const matrix: number[][] = [];

  // Fill in the matrix
  let row = 0;
  let col = 0;
  let photoId = 0;
  while (photoId < input.length) {
    // Check if we reached the end of row
    if (col >= opts.numCols) {
      row++;
      col = 0;
    }

    // Make sure we have this and the next few rows
    while (row + 3 >= matrix.length) {
      matrix.push(new Array(opts.numCols).fill(0));
    }

    // Check if already used
    if (matrix[row][col] & FLAG_USED) {
      col++;
      continue;
    }

    // Use this slot
    matrix[row][col] |= FLAG_USE;

    // Check if previous row has something used
    // or something beside this is used
    // We don't do these one after another
    if (
      !opts.allowBreakout ||
      (row > 0 && matrix[row - 1].some((v) => v & FLAG_USED)) ||
      (col > 0 && matrix[row][col - 1] & FLAG_USED)
    ) {
      photoId++;
      col++;
      continue;
    }

    // Number of photos left
    const numLeft = input.length - photoId - 1;
    // Number of photos needed for perfect fill after using n
    const needFill = (n: number) => opts.numCols - col - 2 + (n / 2 - 1) * (opts.numCols - 2);

    let canUse4 =
      // We have enough space
      row + 1 < matrix.length &&
      col + 1 < opts.numCols &&
      // This cannot end up being a widow (conservative)
      // Also make sure the next row gets fully filled, otherwise looks weird
      (numLeft === needFill(4) || numLeft >= needFill(4) + opts.numCols);

    let canUse6 =
      // Image is portrait
      input[photoId].height > input[photoId].width &&
      // We have enough space
      row + 2 < matrix.length &&
      col + 1 < opts.numCols &&
      // This cannot end up being a widow (conservative)
      // Also make sure the next row gets fully filled, otherwise looks weird
      (numLeft === needFill(6) || numLeft >= needFill(6) + 2 * opts.numCols);

    let canBreakout =
      // First column only
      col === 0 &&
      // Image is landscape
      input[photoId].width > input[photoId].height &&
      // The next row gets filled
      (numLeft === 0 || numLeft >= opts.numCols);

    // Probably folders or tags or faces
    if (input[photoId].forceSquare) {
      // We are square already. Everything below is else-if.
    }

    // Full width breakout
    else if (canBreakout && rand() < (input.length > 0 ? 0.25 : 0.1)) {
      matrix[row][col] |= FLAG_BREAKOUT;
      for (let i = 1; i < opts.numCols; i++) {
        matrix[row][i] |= FLAG_USED;
      }
    }

    // Use 6 vertically
    else if (canUse6 && rand() < 0.2) {
      matrix[row][col] |= FLAG_USE6;
      matrix[row + 1][col] |= FLAG_USED;
      matrix[row + 2][col] |= FLAG_USED;
      matrix[row][col + 1] |= FLAG_USED;
      matrix[row + 1][col + 1] |= FLAG_USED;
      matrix[row + 2][col + 1] |= FLAG_USED;
    }

    // Use 4 box
    else if (canUse4 && rand() < 0.35) {
      matrix[row][col] |= FLAG_USE4;
      matrix[row + 1][col] |= FLAG_USED;
      matrix[row][col + 1] |= FLAG_USED;
      matrix[row + 1][col + 1] |= FLAG_USED;
    }

    // Go ahead
    photoId++;
    col++;
  }

  // Square layout matrix
  const absMatrix: {
    top: number;
    left: number;
    width: number;
    height: number;
  }[] = [];

  let currTop = 0;
  row = 0;
  col = 0;
  photoId = 0;
  while (photoId < input.length) {
    // Check if we reached the end of row
    if (col >= opts.numCols) {
      row++;
      col = 0;
      currTop += opts.rowHeight;
      continue;
    }

    // Skip if used
    if (!(matrix[row][col] & FLAG_USE)) {
      col++;
      continue;
    }

    // Create basic object
    const sqsize = opts.rowHeight;
    const p = {
      top: currTop,
      left: col * sqsize,
      width: sqsize,
      height: sqsize,
      rowHeight: opts.rowHeight,
    };

    // Use twice the space
    const v = matrix[row][col];
    if (v & FLAG_USE4) {
      p.width *= 2;
      p.height *= 2;
      col += 2;
    } else if (v & FLAG_USE6) {
      p.width *= 2;
      p.height *= 3;
      col += 2;
    } else if (v & FLAG_BREAKOUT) {
      p.width *= opts.numCols;
      p.height = (input[photoId].height * p.width) / input[photoId].width;
      p.rowHeight = p.height;
      col += opts.numCols;
    } else {
      col++;
    }

    absMatrix.push(p);
    photoId++;
  }

  return absMatrix;
}

function flagMatrixStr(matrix: number[][], numFlag: number) {
  let str = '';
  for (let i = 0; i < matrix.length; i++) {
    const rstr = matrix[i].map((v) => v.toString(2).padStart(numFlag, '0')).join(' ');
    str += i.toString().padStart(2) + ' | ' + rstr + '\n';
  }
  return str;
}

function mulberry32(a: number) {
  return function () {
    var t = (a += 0x6d2b79f5);
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
